#pragma once

#include <cstdint>
#include <utility>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <boost/unordered_map.hpp>
#include "../crow/common.h"
#include "../crow/http_response.h"
#include "../crow/http_request.h"
#include "../crow/utility.h"
#include "../crow/logging.h"
#include "../crow/websocket.h"

namespace crow {

class BaseRule {
public:
    BaseRule(std::string rule) :
    rule_(std::move(rule)) {
    }

    virtual ~BaseRule() {
    }

    virtual void validate() = 0;
    std::unique_ptr<BaseRule> upgrade() {
        if (rule_to_upgrade_) return std::move(rule_to_upgrade_);
        return {};
    }

    virtual void handle(const request&, response&, const routing_params&) = 0;
    virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) {
        res = response(404);
        res.end();
    }
#ifdef CROW_ENABLE_SSL
    virtual void handle_upgrade(const request&, response& res, SSLAdaptor&&)
    {
        res = response(404);
        res.end();
    }
#endif

    uint32_t get_methods() {
        return methods_;
    }

protected:
    uint32_t methods_ { 1 << (int)HTTPMethod::Get };

    std::string rule_;
    std::string name_;

    std::unique_ptr<BaseRule> rule_to_upgrade_;

    friend class Router;
    template<typename T>
    friend struct RuleParameterTraits;
};

namespace detail {
namespace routing_handler_call_helper {
template<typename T, int Pos>
struct call_pair {
    using type = T;
    static const int pos = Pos;
};

template<typename H1>
struct call_params {
    H1& handler;
    const routing_params& params;
    const request& req;
    response& res;
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename S1, typename S2>
struct call {
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, black_magic::S<Args2...>> {
    void operator()(F cparams) {
        using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<int64_t, NInt>>;
        call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, pushed>()(cparams);
    }
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> {
    void operator()(F cparams) {
        using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<uint64_t, NUint>>;
        call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, pushed>()(cparams);
    }
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, black_magic::S<Args2...>> {
    void operator()(F cparams) {
        using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<double, NDouble>>;
        call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, pushed>()(cparams);
    }
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> {
    void operator()(F cparams) {
        using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<std::string, NString>>;
        call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, pushed>()(cparams);
    }
};

template<typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<>, black_magic::S<Args1...>> {
    void operator()(F cparams) {
        cparams.handler(cparams.req, cparams.res, cparams.params.template get<typename Args1::type>(Args1::pos)...);
    }
};

template<typename Func, typename ... ArgsWrapped>
struct Wrapped {
    template<typename ... Args>
    void set(
    Func f,
    typename std::enable_if<
        !std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value, int
    >::type = 0) {
    handler_ = (
#ifdef CROW_CAN_USE_CPP14
    [f = std::move(f)]
#else
    [f]
#endif
    (const request&, response& res, Args... args) {
        res = response(f(args...));
        res.end();
    });
}

template<typename Req, typename ... Args>
struct req_handler_wrapper {
    req_handler_wrapper(Func f) :
    f(std::move(f)) {
    }

    void operator()(const request& req, response& res, Args ... args) {
        res = response(f(req, args...));
        res.end();
    }

    Func f;
};

template<typename ... Args>
void set(
Func f,
typename std::enable_if<
    std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value &&
    !std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type, response&>::value, int>::type = 0) {
    handler_ = req_handler_wrapper<Args...>(std::move(f));
    /*handler_ = (
     [f = std::move(f)]
     (const request& req, response& res, Args... args){
     res = response(f(req, args...));
     res.end();
     });*/
}

template<typename ... Args>
void set(Func f, typename std::enable_if<std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type,
         const request&>::value && std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type,
         response&>::value, int>::type = 0) {
    handler_ = std::move(f);
}

template<typename ... Args>
struct handler_type_helper {
    using type = std::function<void(const crow::request&, crow::response&, Args...)>;
    using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};

template<typename ... Args>
struct handler_type_helper<const request&, Args...> {
    using type = std::function<void(const crow::request&, crow::response&, Args...)>;
    using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};

template<typename ... Args>
struct handler_type_helper<const request&, response&, Args...> {
    using type = std::function<void(const crow::request&, crow::response&, Args...)>;
    using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};

typename handler_type_helper<ArgsWrapped...>::type handler_;

void operator()(const request& req, response& res, const routing_params& params) {
    detail::routing_handler_call_helper::call<detail::routing_handler_call_helper::call_params<decltype(handler_)>, 0, 0, 0, 0,
    typename handler_type_helper<ArgsWrapped...>::args_type, black_magic::S<> >()(
    detail::routing_handler_call_helper::call_params<decltype(handler_)> { handler_, params, req, res });
}
};

}
}

class WebSocketRule: public BaseRule {
    using self_t = WebSocketRule;
public:
    WebSocketRule(std::string rule) :
    BaseRule(std::move(rule)) {
    }

    void validate() override
    {
    }

    void handle(const request&, response& res, const routing_params&) override
    {
        res = response(404);
        res.end();
    }

    void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override
    {
        new crow::websocket::Connection<SocketAdaptor>(req, std::move(adaptor), open_handler_, message_handler_, close_handler_,
        error_handler_);
    }
#ifdef CROW_ENABLE_SSL
    void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override
    {
        new crow::websocket::Connection<SSLAdaptor>(req, std::move(adaptor), open_handler_, message_handler_, close_handler_, error_handler_);
    }
#endif

    template<typename Func>
    self_t& onopen(Func f) {
        open_handler_ = f;
        return *this;
    }

    template<typename Func>
    self_t& onmessage(Func f) {
        message_handler_ = f;
        return *this;
    }

    template<typename Func>
    self_t& onclose(Func f) {
        close_handler_ = f;
        return *this;
    }

    template<typename Func>
    self_t& onerror(Func f) {
        error_handler_ = f;
        return *this;
    }

protected:
    std::function<void(crow::websocket::connection&)> open_handler_;
    std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_;
    std::function<void(crow::websocket::connection&, const std::string&)> close_handler_;
    std::function<void(crow::websocket::connection&)> error_handler_;
};

template<typename T>
struct RuleParameterTraits {
    using self_t = T;
    WebSocketRule& websocket() {
        auto p = new WebSocketRule(((self_t*)this)->rule_);
        ((self_t*)this)->rule_to_upgrade_.reset(p);
        return *p;
    }

    self_t& name(std::string name) noexcept
    {
        ((self_t*)this)->name_ = std::move(name);
        return (self_t&)*this;
    }

    self_t& methods(HTTPMethod method) {
        ((self_t*)this)->methods_ = 1 << (int)method;
        return (self_t&)*this;
    }

    template<typename ... MethodArgs>
    self_t& methods(HTTPMethod method, MethodArgs ... args_method) {
        methods(args_method...);
        ((self_t*)this)->methods_ |= 1 << (int)method;
        return (self_t&)*this;
    }

};

class DynamicRule: public BaseRule, public RuleParameterTraits<DynamicRule> {
public:

    DynamicRule(std::string rule) :
    BaseRule(std::move(rule)) {
    }

    void validate() override {
        if (!erased_handler_) {
            throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_);
        }
    }

    void handle(const request& req, response& res, const routing_params& params) override {
        erased_handler_(req, res, params);
    }

    template<typename Func>
    void operator()(Func f) {
#ifdef CROW_MSVC_WORKAROUND
        using function_t = utility::function_traits<decltype(&Func::operator())>;
#else
        using function_t = utility::function_traits<Func>;
#endif
        erased_handler_ = wrap(std::move(f), black_magic::gen_seq<function_t::arity>());
    }

    // enable_if Arg1 == request && Arg2 == response
    // enable_if Arg1 == request && Arg2 != resposne
    // enable_if Arg1 != request
#ifdef CROW_MSVC_WORKAROUND
    template <typename Func, size_t ... Indices>
#else
    template<typename Func, unsigned ... Indices>
#endif
    std::function<void(const request&, response&, const routing_params&)> wrap(Func f, black_magic::seq<Indices...>) {
#ifdef CROW_MSVC_WORKAROUND
        using function_t = utility::function_traits<decltype(&Func::operator())>;
#else
        using function_t = utility::function_traits<Func>;
#endif
        if (!black_magic::is_parameter_tag_compatible(black_magic::get_parameter_tag_runtime(rule_.c_str()),
        black_magic::compute_parameter_tag_from_args_list<typename function_t::template arg<Indices>...>::value)) {
            throw std::runtime_error("route_dynamic: Handler type is mismatched with URL parameters: " + rule_);
        }
        auto ret = detail::routing_handler_call_helper::Wrapped<Func, typename function_t::template arg<Indices>...>();
        ret.template set<typename function_t::template arg<Indices>...>(std::move(f));
        return ret;
    }

    template<typename Func>
    void operator()(std::string name, Func&& f) {
        name_ = std::move(name);
        (*this).template operator()<Func>(std::forward(f));
    }

private:
    std::function<void(const request&, response&, const routing_params&)> erased_handler_;

};

template<typename ... Args>
class TaggedRule: public BaseRule, public RuleParameterTraits<TaggedRule<Args...>> {
public:
    using self_t = TaggedRule<Args...>;

    TaggedRule(std::string rule) :
    BaseRule(std::move(rule)) {
    }

    void validate() override {
        if (!handler_) {
            throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_);
        }
    }

    template<typename Func>
    typename std::enable_if<black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type operator()(Func&& f) {
        static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
        black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value ,
        "Handler type is mismatched with URL parameters");
        static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
        "Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");

        handler_ = [f = std::move(f)](const request&, response& res, Args ... args) {
            res = response(f(args...));
            res.end();
    };
}

template<typename Func>
typename std::enable_if<
    !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
    black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value, void
>::type operator()(Func&& f) {
    static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
    black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value,
    "Handler type is mismatched with URL parameters");
    static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<Args>()...))>::value,
    "Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");

    handler_ = [f = std::move(f)](const crow::request& req, crow::response& res, Args ... args) {
        res = response(f(req, args...));
        res.end();
    };
}

template<typename Func>
typename std::enable_if<
    !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
    !black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value, void
>::type operator()(Func&& f) {
    static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
    black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value ||
    black_magic::CallHelper<Func, black_magic::S<crow::request, crow::response&, Args...>>::value,
    "Handler type is mismatched with URL parameters");
    static_assert(std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
    "Handler function with response argument should have void return type");

    handler_ = std::move(f);
}

template<typename Func>
void operator()(std::string name, Func&& f) {
    name_ = std::move(name);
    (*this).template operator()<Func>(std::forward(f));
}

void handle(const request& req, response& res, const routing_params& params) override
{
    detail::routing_handler_call_helper::call<
        detail::routing_handler_call_helper::call_params<decltype(handler_)>, 0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>
    >()(detail::routing_handler_call_helper::call_params<decltype(handler_)> { handler_, params, req,res });
}

private:
std::function<void(const crow::request&, crow::response&, Args...)> handler_;

};

const int RULE_SPECIAL_REDIRECT_SLASH = 1;

class Trie {
public:
    struct Node {
        unsigned rule_index { };
        std::array<unsigned, (int)ParamType::MAX> param_childrens { };
        boost::unordered_map<std::string, unsigned> children;

        bool IsSimpleNode() const {
            return !rule_index && std::all_of(std::begin(param_childrens), std::end(param_childrens), [](unsigned x) {return !x;});
        }
    };

    Trie() :
    nodes_(1) {
    }

private:
    void optimizeNode(Node* node) {
        for (auto x : node->param_childrens) {
            if (!x) continue;
            Node* child = &nodes_[x];
            optimizeNode(child);
        }
        if (node->children.empty()) return;
        bool mergeWithChild = true;
        for (auto& kv : node->children) {
            Node* child = &nodes_[kv.second];
            if (!child->IsSimpleNode()) {
                mergeWithChild = false;
                break;
            }
        }
        if (mergeWithChild) {
            decltype(node->children) merged;
            for (auto& kv : node->children) {
                Node* child = &nodes_[kv.second];
                for (auto& child_kv : child->children) {
                    merged[kv.first + child_kv.first] = child_kv.second;
                }
            }
            node->children = std::move(merged);
            optimizeNode(node);
        }
        else {
            for (auto& kv : node->children) {
                Node* child = &nodes_[kv.second];
                optimizeNode(child);
            }
        }
    }

    void optimize() {
        optimizeNode(head());
    }

public:
    void validate() {
        if (!head()->IsSimpleNode()) throw std::runtime_error("Internal error: Trie header should be simple!");
        optimize();
    }

    std::pair<unsigned, routing_params> find(const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, routing_params* params =
                                             nullptr) const {
        routing_params empty;
        if (params == nullptr) params = &empty;

        unsigned found { };
        routing_params match_params;

        if (node == nullptr) node = head();
        if (pos == req_url.size()) return {node->rule_index, *params};

        auto update_found = [&found, &match_params](std::pair<unsigned, routing_params>& ret)
        {
            if (ret.first && (!found || found > ret.first))
            {
                found = ret.first;
                match_params = std::move(ret.second);
            }
        };

        if (node->param_childrens[(int)ParamType::INT]) {
            char c = req_url[pos];
            if ((c >= '0' && c <= '9') || c == '+' || c == '-') {
                char* eptr;
                errno = 0;
                long long int value = strtoll(req_url.data() + pos, &eptr, 10);
                if (errno != ERANGE && eptr != req_url.data() + pos) {
                    params->int_params.push_back(value);
                    auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::INT]], eptr - req_url.data(), params);
                    update_found(ret);
                    params->int_params.pop_back();
                }
            }
        }

        if (node->param_childrens[(int)ParamType::UINT]) {
            char c = req_url[pos];
            if ((c >= '0' && c <= '9') || c == '+') {
                char* eptr;
                errno = 0;
                unsigned long long int value = strtoull(req_url.data() + pos, &eptr, 10);
                if (errno != ERANGE && eptr != req_url.data() + pos) {
                    params->uint_params.push_back(value);
                    auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::UINT]], eptr - req_url.data(), params);
                    update_found(ret);
                    params->uint_params.pop_back();
                }
            }
        }

        if (node->param_childrens[(int)ParamType::DOUBLE]) {
            char c = req_url[pos];
            if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') {
                char* eptr;
                errno = 0;
                double value = strtod(req_url.data() + pos, &eptr);
                if (errno != ERANGE && eptr != req_url.data() + pos) {
                    params->double_params.push_back(value);
                    auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::DOUBLE]], eptr - req_url.data(), params);
                    update_found(ret);
                    params->double_params.pop_back();
                }
            }
        }

        if (node->param_childrens[(int)ParamType::STRING]) {
            size_t epos = pos;
            for (; epos < req_url.size(); epos++) {
                if (req_url[epos] == '/') break;
            }

            if (epos != pos) {
                params->string_params.push_back(req_url.substr(pos, epos - pos));
                auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::STRING]], epos, params);
                update_found(ret);
                params->string_params.pop_back();
            }
        }

        if (node->param_childrens[(int)ParamType::PATH]) {
            size_t epos = req_url.size();

            if (epos != pos) {
                params->string_params.push_back(req_url.substr(pos, epos - pos));
                auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::PATH]], epos, params);
                update_found(ret);
                params->string_params.pop_back();
            }
        }

        for (auto& kv : node->children) {
            const std::string& fragment = kv.first;
            const Node* child = &nodes_[kv.second];

            if (req_url.compare(pos, fragment.size(), fragment) == 0) {
                auto ret = find(req_url, child, pos + fragment.size(), params);
                update_found(ret);
            }
        }

        return {found, match_params};
    }

    void add(const std::string& url, unsigned rule_index) {
        unsigned idx { 0 };

        for (unsigned i = 0; i < url.size(); i++) {
            char c = url[i];
            if (c == '<') {
                static struct ParamTraits {
                    ParamType type;
                    std::string name;
                } paramTraits[] = { { ParamType::INT, "<int>" }, { ParamType::UINT, "<uint>" }, { ParamType::DOUBLE, "<float>" }, {
                        ParamType::DOUBLE, "<double>" }, { ParamType::STRING, "<str>" }, { ParamType::STRING, "<string>" }, { ParamType::PATH,
                        "<path>" }, };

                for (auto& x : paramTraits) {
                    if (url.compare(i, x.name.size(), x.name) == 0) {
                        if (!nodes_[idx].param_childrens[(int)x.type]) {
                            auto new_node_idx = new_node();
                            nodes_[idx].param_childrens[(int)x.type] = new_node_idx;
                        }
                        idx = nodes_[idx].param_childrens[(int)x.type];
                        i += x.name.size();
                        break;
                    }
                }

                i--;
            }
            else {
                std::string piece(&c, 1);
                if (!nodes_[idx].children.count(piece)) {
                    auto new_node_idx = new_node();
                    nodes_[idx].children.emplace(piece, new_node_idx);
                }
                idx = nodes_[idx].children[piece];
            }
        }
        if (nodes_[idx].rule_index) throw std::runtime_error("handler already exists for " + url);
        nodes_[idx].rule_index = rule_index;
    }

private:
    void debug_node_print(Node* n, int level) {
        for (int i = 0; i < (int)ParamType::MAX; i++) {
            if (n->param_childrens[i]) {
                CROW_LOG_DEBUG << std::string(2 * level, ' ') /*<< "("<<n->param_childrens[i]<<") "*/;
                switch ((ParamType)i) {
                case ParamType::INT:
                    CROW_LOG_DEBUG << "<int>";
                    break;
                case ParamType::UINT:
                    CROW_LOG_DEBUG << "<uint>";
                    break;
                case ParamType::DOUBLE:
                    CROW_LOG_DEBUG << "<float>";
                    break;
                case ParamType::STRING:
                    CROW_LOG_DEBUG << "<str>";
                    break;
                case ParamType::PATH:
                    CROW_LOG_DEBUG << "<path>";
                    break;
                default:
                    CROW_LOG_DEBUG << "<ERROR>";
                    break;
            }
            debug_node_print(&nodes_[n->param_childrens[i]], level + 1);
            }
        }
        for (auto& kv : n->children) {
            CROW_LOG_DEBUG << std::string(2 * level, ' ') /*<< "(" << kv.second << ") "*/<< kv.first;
            debug_node_print(&nodes_[kv.second], level + 1);
        }
    }

public:
    void debug_print() {
        debug_node_print(head(), 0);
    }

    private:
    const Node* head() const {
        return &nodes_.front();
    }

    Node* head() {
        return &nodes_.front();
    }

    unsigned new_node() {
        nodes_.resize(nodes_.size() + 1);
        return nodes_.size() - 1;
    }

    std::vector<Node> nodes_;
};

class Router {
public:
    Router() :
    rules_(2) {
    }

    DynamicRule& new_rule_dynamic(const std::string& rule) {
        auto ruleObject = new DynamicRule(rule);

        internal_add_rule_object(rule, ruleObject);

        return *ruleObject;
    }

    template<uint64_t N>
    typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule) {
        using RuleT = typename black_magic::arguments<N>::type::template rebind<TaggedRule>;
        auto ruleObject = new RuleT(rule);

        internal_add_rule_object(rule, ruleObject);

        return *ruleObject;
    }

    void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject) {
        rules_.emplace_back(ruleObject);
        trie_.add(rule, rules_.size() - 1);

        // directory case:
        //   request to `/about' url matches `/about/' rule
        if (rule.size() > 1 && rule.back() == '/') {
            std::string rule_without_trailing_slash = rule;
            rule_without_trailing_slash.pop_back();
            trie_.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH);
        }
    }

    void validate() {
        trie_.validate();
        for (auto& rule : rules_) {
            if (rule) {
                auto upgraded = rule->upgrade();
                if (upgraded) rule = std::move(upgraded);
                rule->validate();
            }
        }
    }

    template<typename Adaptor>
    void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) {
        auto found = trie_.find(req.url);
        unsigned rule_index = found.first;
        if (!rule_index) {
            CROW_LOG_DEBUG << "Cannot match rules " << req.url;
            res = response(404);
            res.end();
            return;
        }

        if (rule_index >= rules_.size()) throw std::runtime_error("Trie internal structure corrupted!");

        if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) {
            CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url;
            res = response(301);

            // TODO absolute url building
            if (req.get_header_value("Host").empty()) {
                res.add_header("Location", req.url + "/");
            }
            else {
                res.add_header("Location", "http://" + req.get_header_value("Host") + req.url + "/");
            }
            res.end();
            return;
        }

        if ((rules_[rule_index]->get_methods() & (1 << (uint32_t)req.method)) == 0) {
            CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "("
            << (uint32_t)req.method << ") / " << rules_[rule_index]->get_methods();
            res = response(404);
            res.end();
            return;
        }

        CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules_[rule_index]->rule_ << "' " << (uint32_t)req.method << " / "
        << rules_[rule_index]->get_methods();

        // any uncaught exceptions become 500s
        try {
            rules_[rule_index]->handle_upgrade(req, res, std::move(adaptor));
        }
        catch(std::exception& e) {
            CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
            res = response(500);
            res.end();
            return;
        }
        catch(...) {
            CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
            res = response(500);
            res.end();
            return;
        }
    }

    void handle(const request& req, response& res) {
        auto found = trie_.find(req.url);

        unsigned rule_index = found.first;

        if (!rule_index) {
            CROW_LOG_DEBUG << "Cannot match rules " << req.url;
            res = response(404);
            res.end();
            return;
        }

        if (rule_index >= rules_.size()) throw std::runtime_error("Trie internal structure corrupted!");

        if (rule_index == RULE_SPECIAL_REDIRECT_SLASH) {
            CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url;
            res = response(301);

            // TODO absolute url building
            if (req.get_header_value("Host").empty()) {
                res.add_header("Location", req.url + "/");
            }
            else {
                res.add_header("Location", "http://" + req.get_header_value("Host") + req.url + "/");
            }
            res.end();
            return;
        }

        if ((rules_[rule_index]->get_methods() & (1 << (uint32_t)req.method)) == 0) {
            CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "("
            << (uint32_t)req.method << ") / " << rules_[rule_index]->get_methods();
            res = response(404);
            res.end();
            return;
        }

        CROW_LOG_DEBUG << "Matched rule '" << rules_[rule_index]->rule_ << "' " << (uint32_t)req.method << " / "
        << rules_[rule_index]->get_methods();

        // any uncaught exceptions become 500s
        try {
            rules_[rule_index]->handle(req, res, found.second);
        }
        catch(std::exception& e) {
            CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
            res = response(500);
            res.end();
            return;
        }
        catch(...) {
            CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
            res = response(500);
            res.end();
            return;
        }
    }

    void debug_print() {
        trie_.debug_print();
    }

private:
    std::vector<std::unique_ptr<BaseRule>> rules_;
    Trie trie_;
};

}

